﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Rage.ModelFramework
{
    public class AlacrityStats
    {
        public double AlacrityRating;
        public double Alacrity;

        public double[] AlacrityProcUptimes;
        public int[] AlacrityProcRatings;
        public double[] AlacrityProcMultipliers;

        public double AlacritiedSecond;
        public double AlacritiedGCD;

        public AlacrityStats(Stats stats, KeyValuePair<double, SpecialEffect>[] alacrityProcs)
            : this(stats.AlacrityRating, stats.AlacrityRating, alacrityProcs)
        {}

        public AlacrityStats(double alacrity, double alacrityRating, KeyValuePair<double, SpecialEffect>[] alacrityProcs)
        {
            Alacrity = alacrity;
            AlacrityRating = alacrityRating;

            AlacrityProcUptimes = new double[alacrityProcs.Length];
            AlacrityProcRatings = new int[alacrityProcs.Length];
            AlacrityProcMultipliers = new double[alacrityProcs.Length];

            for (int i = 0; i < alacrityProcs.Length; ++i)
            {
                AlacrityProcUptimes[i] = alacrityProcs[i].Key;
                AlacrityProcRatings[i] = (int)alacrityProcs[i].Value.Stats.AlacrityRating;
                AlacrityProcMultipliers[i] = 1 + alacrityProcs[i].Value.Stats.Alacrity;
            }

            // avoid GCD cap
            AlacritiedSecond = computeAlacritiedCastTimeRaw(1 << 16) / 65.536;

            AlacritiedGCD = computeAlacritiedCastTimeRaw(1500);
        }

        public double ComputeTicks(double baseTickRateMS, double baseDurationMS)
        {
            double duration, tps;
            return ComputeTicks(baseTickRateMS, baseDurationMS, out duration, out tps);
        }

        public double ComputeTicks(double baseTickRateMS, double baseDurationMS, out double duration)
        {
            double tps;
            return ComputeTicks(baseTickRateMS, baseDurationMS, out duration, out tps);
        }

        public double ComputeTicks(double baseTickRateMS, double baseDurationMS, out double duration, out double tps)
        {
            //tickRateMS = 0;
            double ticks = 0;
            tps = 0;
            duration = 0;

            int n = AlacrityProcUptimes.Length;
            int maxmask = 1 << n;
            for (int i = 0; i < maxmask; ++i)
            {
                int alacrityRating = 0;
                double alacrityFactor = (1 + Alacrity);
                double fraction = 1.0;
                for (int j = 0; j < n; ++j)
                {
                    if ((i & (1 << j)) != 0)
                    {
                        alacrityRating += AlacrityProcRatings[j];
                        alacrityFactor *= AlacrityProcMultipliers[j];
                        fraction *= AlacrityProcUptimes[j];
                    }
                    else
                        fraction *= 1 - AlacrityProcUptimes[j];
                }

                // spell alacrity and physical alacrity are actually identically computed in Cataclysm
                double alacrityRatingMult = 1.0f + (1f - StatConversion.GetAlacrityModifierFromRating((float)(AlacrityRating + alacrityRating)));
                alacrityFactor *= alacrityRatingMult;
                double curTickRateMS = baseTickRateMS / alacrityFactor;
                double curTicks = (double)Math.Ceiling(baseDurationMS / (double)Math.Round(curTickRateMS) - 0.5f);

                //tickRateMS += fraction * curTickRateMS;
                ticks += fraction * curTicks;
                tps += fraction * (1.0 / curTickRateMS);
                duration += fraction * curTicks * curTickRateMS;
            }
            duration /= 1000.0;
            tps *= 1000.0;
            return ticks;
        }

        public double ComputeAlacritiedCastTime(double timeMS)
        {
            if (timeMS <= 1000)
                return 1.0;
            if (timeMS == 1500)
                return AlacritiedGCD;
            return computeAlacritiedCastTimeRaw(timeMS);
        }

        private double computeAlacritiedCastTimeRaw(double timeMS)
        {
            double alacritiedTimeMS = 0;

            int n = AlacrityProcUptimes.Length;
            int maxmask = 1 << n;
            for (int i = 0; i < maxmask; ++i)
            {
                int alacrityRating = 0;
                double alacrityFactor = (1 + Alacrity);
                double fraction = 1.0;
                for (int j = 0; j < n; ++j)
                {
                    if ((i & (1 << j)) != 0)
                    {
                        alacrityRating += AlacrityProcRatings[j];
                        alacrityFactor *= AlacrityProcMultipliers[j];
                        fraction *= AlacrityProcUptimes[j];
                    }
                    else
                        fraction *= 1 - AlacrityProcUptimes[j];
                }

                double alacrityRatingMult = 1.0f + (1f - StatConversion.GetAlacrityModifierFromRating((float)(AlacrityRating + alacrityRating)));
                alacrityFactor *= alacrityRatingMult;

                alacritiedTimeMS += fraction * Math.Max(timeMS / alacrityFactor, 1000);
            }
            return alacritiedTimeMS / 1000.0;
        }
    }
}
